package com.zxhhyj.music.logic.repository

import android.provider.MediaStore
import com.funny.data_saver.core.mutableDataSaverListStateOf
import com.github.promeg.pinyinhelper.Pinyin
import com.zxhhyj.music.MainApplication
import com.zxhhyj.music.logic.bean.Folder
import com.zxhhyj.music.logic.bean.SongBean
import com.zxhhyj.music.logic.config.DataSaverUtils
import com.zxhhyj.music.logic.utils.createSongBean
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.newFixedThreadPoolContext
import kotlinx.coroutines.withContext
import java.io.File


/**
 * Android 媒体库仓库类，用于管理媒体库中的歌曲和文件夹数据。
 */
object AndroidMediaLibRepository {

    /**
     * 隐藏的歌曲列表
     */
    var hideSongs by mutableDataSaverListStateOf(
        dataSaverInterface = DataSaverUtils,
        key = "hide_songs",
        initialValue = listOf<SongBean>()
    )
        private set

    /**
     * 文件夹列表
     */
    var folders by mutableDataSaverListStateOf(
        dataSaverInterface = DataSaverUtils,
        key = "folders",
        initialValue = listOf<Folder>()
    )
        private set

    /**
     * 隐藏的文件夹列表
     */
    var hideFolders by mutableDataSaverListStateOf(
        dataSaverInterface = DataSaverUtils, key = "hide_folders", initialValue = listOf<Folder>()
    )
        private set

    /**
     * 歌曲列表
     */
    val songs: List<SongBean>
        get() = folders.flatMap { it.songs }
            .filter { it !in hideSongs }
            .sortedBy { song ->
                when (SettingRepository.SongSortEnum.entries[SettingRepository.SongSort]) {
                    SettingRepository.SongSortEnum.SONG_NAME -> Pinyin.toPinyin(song.songName[0])
                    SettingRepository.SongSortEnum.SONG_DURATION -> song.duration
                    SettingRepository.SongSortEnum.SINGER_NAME -> Pinyin.toPinyin(
                        song.artistName?.get(
                            0
                        ) ?: Char.MIN_VALUE
                    )

                    SettingRepository.SongSortEnum.DATE_MODIFIED -> song.dateModified
                }.toString()
            }.let {
                if (SettingRepository.EnableDescending) {
                    it.reversed()
                } else {
                    it
                }
            }


    val artists
        get() = songs.distinctBy { it.artistName }.map { it.artistName }

    val albums
        get() = songs.distinctBy { it.albumName }.map { it.albumName }

    object Album {
        operator fun get(albumName: String) =
            songs.filter { it.albumName == albumName }
    }

    object Artist {
        operator fun get(artistName: String) =
            songs.filter { it.artistName == artistName }
    }

    /**
     * 扫描媒体库中的歌曲
     */
    @OptIn(DelicateCoroutinesApi::class)
    suspend fun scanMedia() {
        withContext(Dispatchers.IO) {
            val selectionBuilder = StringBuilder("${MediaStore.Audio.Media.IS_MUSIC}!=0")
            val formatCollection = listOf(
                "audio/x-wav", "audio/ogg", "audio/aac", "audio/midi"
            )
            for (i in formatCollection) {
                selectionBuilder.append(" or ${MediaStore.Audio.Media.MIME_TYPE}='$i'")
            }
            val query = MainApplication.context.contentResolver.query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                null,
                selectionBuilder.toString(),
                null,
                null
            )
            val cursorMap = mutableMapOf<String, Long>()
            query?.use { cursor ->
                while (cursor.moveToNext()) {
                    val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)
                    val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
                    val data = cursor.getString(dataIndex)
                    val id = cursor.getLong(idIndex)
                    cursorMap[data] = id
                }
            }
            val dispatcher =
                newFixedThreadPoolContext(Runtime.getRuntime().availableProcessors(), "ScanMedia")
            val scanSongs = cursorMap.map {
                async(dispatcher) {
                    runCatching { createSongBean(it.key, it.value) }.getOrNull()
                }
            }.awaitAll().mapNotNull { it }
            dispatcher.close()
            folders = scanSongs.asSequence()
                .distinctBy { it.data }
                .mapNotNull { File(it.data).parent }
                .distinct()
                .map { path ->
                    Folder(
                        path,
                        scanSongs.filter { File(it.data).parent == path })
                }
                .filter { folder ->
                    folder !in hideFolders && (SettingRepository.EnableExcludeSongsUnderOneMinute && !folder.songs.none { it.duration != null && it.duration > 60000 })
                }
                .toList()
        }
    }

    /**
     * 隐藏指定文件夹
     *
     * @param folder 要隐藏的文件夹
     */
    fun hideFolder(folder: Folder) {
        if (folders.any { it.path == folder.path }) {
            folders = folders - folder
            hideFolders = hideFolders + folder
        }
    }

    /**
     * 取消隐藏指定文件夹
     *
     * @param folder 要取消隐藏的文件夹
     */
    fun unHideFolder(folder: Folder) {
        if (hideFolders.any { it.path == folder.path }) {
            folders = folders + folder
            hideFolders = hideFolders - folder
        }
    }

    /**
     * 隐藏指定歌曲
     *
     * @param song 要隐藏的歌曲
     */
    fun hideSong(song: SongBean) {
        hideSongs = hideSongs + song
    }

    /**
     * 取消隐藏指定歌曲
     *
     * @param song 要取消隐藏的歌曲
     */
    fun unHideSong(song: SongBean) {
        hideSongs = hideSongs - song
    }

    /**
     * 添加歌曲到媒体库
     *
     * @param song 要添加的歌曲
     */
    fun addSong(song: SongBean) {
        hideSongs = hideSongs - song
    }

    /**
     * 从媒体库中移除指定歌曲（准确的来说，应该只能删除外部添加的歌曲，媒体库中的歌曲如果使用这种方法删除最后在刷新媒体库时还好出现）
     *
     * @param song 要移除的歌曲
     */
    fun removeSong(song: SongBean) {
        folders = folders.map {
            if (it.songs.contains(song)) {
                return@map it.copy(
                    path = it.path,
                    songs = it.songs.toMutableList().apply { remove(song) })
            }
            it
        }
        hideSongs = hideSongs - song
    }

}